import { initRandomSeed, randomIntRange } from "../random";
import Vector2 from "../vector";
import Board, { BoardCell } from "./board";
import PillState, { BigPillState } from "./pillState";
import GameButton from "./input/gameButton";
import signals from "signals";

export default class GameStateSystem {
  readonly board: Board = new Board(16, 8); 
  readonly pills: PillState[] = [];

  private nowPillFixed = false;
  private nowPill: PillState;
  get controlledPill() { return this.nowPill; }

  stepTimer = 0;
  pillsToDrop = new Set<PillState>();

  pillDeleted = new signals.Signal();
  pillCreated = new signals.Signal();
  pillReplaced = new signals.Signal();

  constructor() {
    initRandomSeed(10);
  }

  start() {
    this.nowPill = this.createPill();
    this.nowPillFixed = false;
  }

  update() {
    if (this.nowPillFixed && this.pillsToDrop.size == 0) {
      this.nowPill = this.createPill();
      this.nowPillFixed = false;
    }
  
    this.stepTimer++;
    if (this.stepTimer > 120) {
      this.stepUpdate();
      this.stepTimer = 0;
    }
  }

  private stepUpdate() {
    if (this.pillsToDrop.size) {
      this.pillsToDrop.forEach((pill, i) => {
        if (pill.canFall(1)) {
          pill.fall(1);
        } else {
          this.pillsToDrop.delete(pill);
          this.clearCells(pill);
        }
      });
    } else {
      if (this.nowPill.canFall(1)) {
        this.nowPill.fall(1);
      } else {
        this.clearCells(this.nowPill);
        this.nowPillFixed = true;
      }
    }
  }

  applyButtonInput(button: GameButton) {
    switch (button) {
      case GameButton.Left:
        if (this.nowPill.canMove(-1, 0)) {
          this.nowPill.move(-1, 0);
        }
        break;
      case GameButton.Right:
        if (this.nowPill.canMove(1, 0)) {
          this.nowPill.move(1, 0);
          break;            
        }
        break;
      case GameButton.Down:
        if (this.nowPill.canMove(0, 1)) {
          this.nowPill.move(0, 1);
        }
        break;
      case GameButton.Rotate:
        if (this.nowPill.canRotate(true)) {
          this.nowPill.rotate(true);
        }
        break;
      case GameButton.HardDrop:
        while (this.nowPill.canFall(1)) {
          this.nowPill.fall(1);
        }
        break;
    }
  }

  private clearCells(centerPill: PillState) {
    const { board } = this;
    const cellsToClear = [];
    for (let r = 0; r < 2; r++) {
      for (let c = 0; c < 2; c++) {
        const val = centerPill.matrix[r][c];
        if (!val) continue;

        const rayX = centerPill.gridPos.x + c;
        const rayY = centerPill.gridPos.y + r;
        let linked;

        linked = [];
        let x = rayX;
        while (board.isBoundPos(x, rayY) && board.get(x, rayY).val == val) {
          linked.push(board.get(x, rayY));
          x++;
        }
        x = rayX - 1;
        while (board.isBoundPos(x, rayY) && board.get(x, rayY).val == val) {
          linked.push(board.get(x, rayY));
          x--;
        }
        if (linked.length >= 4) {
          cellsToClear.push(...linked);
        }

        linked = [];
        let y = rayY;
        while (board.isBoundPos(rayX, y) && board.get(rayX, y).val == val) {
          linked.push(board.get(rayX, y));
          y++;
        }
        y = rayY - 1;
        while (board.isBoundPos(rayX, y) && board.get(rayX, y).val == val) {
          linked.push(board.get(rayX, y));
          y--;
        }
        if (linked.length >= 4) {
          cellsToClear.push(...linked);
        }
      }
    }

    const pillsToDel = [];
    cellsToClear.forEach((cell) => {
      board.setCell(cell.pos.x, cell.pos.y, BoardCell.Empty);
      if (cell.obj instanceof BigPillState) {
        cell.obj.part(cell.val);
      } else {
        pillsToDel.push(cell.obj);
      }
    });

    const aboveCellAddToDrop = (x: number, y: number) => {
      let aboveCell;
      while (y >= 0 &&
        (aboveCell = this.board.get(x, y)).obj instanceof PillState) {
        this.pillsToDrop.add(aboveCell.obj);
        y--;
      }
    };

    this.pills.forEach((pill) => {
      if (pill instanceof BigPillState) {
        const smallPill = pill.tryToSmall();
        if (smallPill) {
          smallPill.setBoardState(true);
          this.pills.push(smallPill);
          this.pillReplaced.dispatch(smallPill);

          if (smallPill.canFall(1)) {
            this.pillsToDrop.add(smallPill);
            aboveCellAddToDrop(smallPill.gridPos.x, smallPill.gridPos.y - 1);
          }
        }
      }
    });   

    cellsToClear.forEach((cell) => {
      const x = cell.pos.x;
      let y = cell.pos.y - 1;
      let aboveCell;
      while (y >= 0 && (aboveCell = this.board.get(x, y)) == BoardCell.Empty) {
        y--;
      }
      if (aboveCell.obj instanceof PillState) {
        const pill = aboveCell.obj;
        if (pill.canFall(1)) {
          this.pillsToDrop.add(pill);
          aboveCellAddToDrop(x, y - 1);
        }
      }
    });

    pillsToDel.push(...this.pills.filter(
      (pill) => pill instanceof BigPillState && pill.isSmall()));

    // remove from pills
    pillsToDel.forEach((pill) => {
      const i = this.pills.findIndex((it) => it == pill);
      this.pills.splice(i, 1);
      this.pillDeleted.dispatch(pill);
    });

    return cellsToClear.length > 0;
  }

  private createPill() {
    const pill = new BigPillState(
      new Vector2((this.board.cols - 2) / 2, 0),
      [randomIntRange(1, 4), randomIntRange(1, 4)],
      this.board
    );
    pill.setBoardState(true);
    this.pills.push(pill);
    this.pillCreated.dispatch(pill);
    return pill;
  }
}